/*
 * dump.c
 *
 * $Revision: 1.6 $
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include "decl.h"
#include "code.h"

/****************************************
  変数いろいろ
****************************************/
static FILE *outfp = stdout;
static long last_tempo = DEFAULT_TEMPO;
long inpline = 1;
static unsigned char *trackbuf[MAXTRACK];
long chidx[MAXTRACK];
long waitcount[MAXTRACK];

/****************************************
  テンポ -> タイムベース計算
****************************************/
long
tempo(long base)
{
    last_tempo = base;
    return TEMPOBASE / (TIMEBASE * base);
}

/****************************************
  タイムベース -> テンポ計算
****************************************/
long
rtempo(long tem)
{
    return TEMPOBASE / (TIMEBASE * tem);
}

/****************************************
  バッファに書き込み
****************************************/
void
putbuf(int track, ...)
{
    va_list ap;
    long code, lval;

    va_start(ap, track);
    code = va_arg(ap, long);
    /* コードによるバッファへの書き込み */
    switch (code) {
	/* none arg */
      case CODE_EOM:
      case CODE_KEYOFF:
      case CODE_INFLOOP:
	trackbuf[track][chidx[track]++] = code;
	break;
	/* byte arg */
      case CODE_PROG:
      case CODE_NOTE:
      case CODE_VOLUME:
      case CODE_CHVOL:
      case CODE_PANVOL:
	trackbuf[track][chidx[track]++] = code;
	lval = va_arg(ap, long);
	trackbuf[track][chidx[track]++] = lval;
	break;
	/* short arg */
      case CODE_WAIT:
      case CODE_TEMPO:
	trackbuf[track][chidx[track]++] = code;
	lval = va_arg(ap, long);
	trackbuf[track][chidx[track]++] = lval & 0xff;
	trackbuf[track][chidx[track]++] = (lval >> 8) & 0xff;
	if (code == CODE_WAIT)
	  waitcount[track] += lval;
	break;
      default:
	/* 例外: C6230 レジスタ書き込みは上位ニブルがコマンド */
	if ((code & 0xF0) == CODE_REGWR) {
	    trackbuf[track][chidx[track]++] = code;
	    lval = va_arg(ap, long);
	    trackbuf[track][chidx[track]++] = lval;
	    break;
	}
	fprintf(stderr, "%s: internal buffer error[%02lx]\n", progname, code);
	exit(EXIT_FAILURE);
	/* NOTREACHED */
    }
    va_end(ap);
    /* トラックバッファオーバーランチェック */
    {
	int i;

	for (i = TRACK_MIN; i < MAXTRACK; i++)
	  if (chidx[i] > TRACKBUFSIZ) {
	      fprintf(stderr, "%s: %ld: track buffer overrun\n", infile, inpline);
	      exit(EXIT_FAILURE);
	  }
    }
}

/****************************************
  コード出力
****************************************/
static void
output_sub(long code, ...)
{
    va_list ap;
    long lval;

    va_start(ap, code);
    /* コードによる出力 */
    switch (code) {
	/* none arg */
      case CODE_EOM:
	output_sub(CODE_KEYOFF); /* 念のためキーオフ */
	/* THROUGH */
      case CODE_KEYOFF:
      case CODE_INFLOOP:
	fprintf(outfp, "\tdb\t$%02lx", code);
	switch (code) {
	  case CODE_EOM:
	    fprintf(outfp, "\t\t; end of data");
	    break;
	  case CODE_KEYOFF:
	    fprintf(outfp, "\t\t; key off");
	    break;
	  case CODE_INFLOOP:
	    fprintf(outfp, "\t\t; infinity loop");
	    break;
	}
	break;
	/* byte arg */
      case CODE_PROG:
      case CODE_NOTE:
      case CODE_VOLUME:
      case CODE_CHVOL:
      case CODE_PANVOL:
	lval = va_arg(ap, long);
	fprintf(outfp, "\tdb\t$%02lx,$%02lx", code, lval);
	switch (code) {
	  case CODE_PROG:
	    fprintf(outfp, "\t\t; program %ld", lval);
	    break;
	  case CODE_NOTE:
	    fprintf(outfp, "\t\t; note %s", notetbl[lval]);
	    break;
	  case CODE_VOLUME:
	    fprintf(outfp, "\t\t; volume (%2ld,%2ld)", (lval >> 4) & 0x0f, lval & 0x0f);
	    break;
	  case CODE_CHVOL:
	    fprintf(outfp, "\t\t; channel volume %ld", lval);
	    break;
	  case CODE_PANVOL:
	    fprintf(outfp, "\t\t; pan volume (%2ld,%2ld)", (lval >> 4) & 0x0f, lval & 0x0f);
	    break;
	}
	break;
	/* short arg */
      case CODE_WAIT:
      case CODE_TEMPO:
	lval = va_arg(ap, long);
	fprintf(outfp, "\tdb\t$%02lx,$%02lx,$%02lx", code, lval & 0xFF, (lval >> 8) & 0xFF);
	switch (code) {
	  case CODE_WAIT:
	    fprintf(outfp, "\t; wait %ld", lval);
	    break;
	  case CODE_TEMPO:
	    fprintf(outfp, "\t; tempo %ld", rtempo(lval));
	    break;
	}
	break;
      default:
	/* 例外: C6230 レジスタ書き込みは上位ニブルがコマンド */
	if ((code & 0xF0) == CODE_REGWR) {
	    lval = va_arg(ap, long);
	    fprintf(outfp, "\tdb\t$%02lx,$%02lx", code, lval);
	    fprintf(outfp, "\t\t; C6230 direct write r%ld <- %ld", code & 0x0F, lval);
	    break;
	}
	fprintf(stderr, "%s: %ld: internal code error[%lx]\n", infile, inpline, code);
	exit(EXIT_FAILURE);
	/* NOTREACHED */
    }
    fprintf(outfp, "\n");
    va_end(ap);
}

/****************************************
  指定されたファイルディスクリプタに出力
****************************************/
static void
output(int track)
{
    long i;
    unsigned char hval, lval;

    fprintf(outfp, ".track%d:\n", track);
    for (i = 0; i < chidx[track]; i++) {
	unsigned char code = trackbuf[track][i];

	switch (code) {
	  case CODE_EOM:
	  case CODE_KEYOFF:
	  case CODE_INFLOOP:
	    output_sub(code);
	    break;
	  case CODE_PROG:
	  case CODE_NOTE:
	  case CODE_VOLUME:
	  case CODE_CHVOL:
	  case CODE_PANVOL:
	    output_sub(code, trackbuf[track][++i]);
	    break;
	  case CODE_WAIT:
	  case CODE_TEMPO:
	    lval = trackbuf[track][++i];
	    hval = trackbuf[track][++i];
	    output_sub(code, (hval << 8) | lval);
	    break;
	  default:
	    /* 例外: C6230 レジスタ書き込みは上位ニブルがコマンド */
	    if ((code & 0xF0) == CODE_REGWR) {
		output_sub(code, trackbuf[track][++i]);
		break;
	    }
	    fprintf(stderr, "%s: internal output error[%02x]\n", progname, code);
	    exit(1);
	}
    }
}

/****************************************
  デフォルト設定出力
****************************************/
static void
default_output()
{
    int track;

    putbuf(TRACK_MIN, CODE_TEMPO, tempo(DEFAULT_TEMPO)); /* デフォルトテンポ設定 */
    putbuf(TRACK_MIN, CODE_VOLUME, VOLUME(DEFAULT_VOLUME, DEFAULT_VOLUME)); /* メイン音量設定 */
    putbuf(TRACK_MIN, CODE_CHVOL, DEFAULT_CHVOL); /* チャンネル音量設定 */
    for (track = TRACK_MIN; track < MAXTRACK; track++)
      putbuf(track, CODE_PANVOL, PANVOL(DEFAULT_PANVOL, DEFAULT_PANVOL)); /* チャンネルパン音量設定 */

}

/****************************************
  ファイルから 1行読み込み、結果を吐き出す
****************************************/
void
dump()
{
    FILE *infp;
    char *inbuf;
    long track;

    if (outf) {
	if ((outfp = fopen(outfile, "w")) == NULL) {
	    fprintf(stderr, "%s: %s: %s\n", progname, outfile, strerror(errno));
	    exit(EXIT_FAILURE);
	}
    }

    inbuf = xmalloc(LINEBUFSIZ);
    if ((infp = fopen(infile, "rb")) == NULL) {
	fprintf(stderr, "%s: %s: %s\n", progname, infile, strerror(errno));
	exit(EXIT_FAILURE);
    }

    for (track = TRACK_MIN; track < MAXTRACK; track++)
      trackbuf[track] = xmalloc(TRACKBUFSIZ);

    parse_init();
    default_output();

    while (fgets(inbuf, LINEBUFSIZ, infp) != NULL) {
	parse(inbuf);
	inpline++;
    }
    fclose(infp);
    free(inbuf);

    if (labelf)
      fprintf(outfp, "%s::\n", glabel);
    for (track = TRACK_MIN; track < MAXTRACK; track++) {
	fprintf(outfp, "\tdw\t.track%ld\n", track);
    }
    for (track = TRACK_MIN; track < MAXTRACK; track++) {
	output(track);
	output_sub(CODE_EOM);
    }
    fclose(outfp);

    if (verbose)
      for (track = TRACK_MIN; track < MAXTRACK; track++) {
	  fprintf(stderr, "track%ld count: %ld\n", track, waitcount[track]);
      }
}
